/**
 *
 */
package commons.beancopier;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


import org.apache.commons.beanutils.PropertyUtils;

import com.tmall.common.beancopier.internal.ArrayPropertyCopier;
import com.tmall.common.beancopier.internal.BasicPropertyCopier;
import com.tmall.common.beancopier.internal.CollectionPropertyCopier;
import com.tmall.common.beancopier.internal.MapPropertyCopier;
import com.tmall.common.beancopier.internal.PrimitivePropertyCopier;
import com.tmall.common.beancopier.internal.TaobaoBeanPropertyCopier;
import com.tmall.common.beancopier.mapper.BeanMapperUtils;
import com.tmall.common.beancopier.mapper.DefaultPropertiesMapperBuilder;
import com.tmall.common.beancopier.mapper.PropertiesMapper;
import com.tmall.common.beancopier.mapper.PropertiesMapperBuilder;

/**
 * @author 陌铭(Phenix Huang)
 *
 */
public final class BeanCopier  {

    private static final BeanCopier copier = new BeanCopier(new DefaultPropertiesMapperBuilder());

    private List<PropertyCopier> copierList = new ArrayList<PropertyCopier>();

    private final PropertiesMapperBuilder builder ;

    public BeanCopier(PropertiesMapperBuilder builder) {
        this.builder = builder ;
        resetDefaultCopierList();
    }

    public static final BeanCopier getDefaultCopier() {
        return copier ;
    }

    public void resetDefaultCopierList() {
        copierList.clear();
        copierList.add(new PrimitivePropertyCopier(builder, this));
        copierList.add(new ArrayPropertyCopier(builder, this));
        copierList.add(new MapPropertyCopier(builder, this));
        copierList.add(new CollectionPropertyCopier(builder, this));
        copierList.add(new TaobaoBeanPropertyCopier(builder, this));
        copierList.add(new BasicPropertyCopier(builder, this));
    }

    public void setPropertyCopierList(List<PropertyCopier> copierList) {
        copierList.clear();
        copierList.addAll(copierList);
    }

    /**
     * A wrapper for copyProperties method, just for put a null as a parent.
     * @param dest
     * @param src
     * @throws Exception
     */
    public final void copyProperties(Object dest, Object src) throws Exception {
        copyProperties(null, dest, src);
    }


    /**
     * copy property value from src to dest.it will base on the property mapper.
     * @param destParent the dest parent object is for aoviding circular dependency.
     * @param dest
     * @param src
     * @throws Exception
     */
    public final void copyProperties(Object destParent, Object dest, Object src) throws Exception {
        if (dest == null || src == null) {
            return ;
        }
        Class<?> clz = src.getClass();
        Map<String, String> propertyMap = BeanMapperUtils.getPropertyNameMap(clz);
        PropertiesMapper mapper = builder.getMapper(clz);
        if (mapper == null) {
            return ;
        }
        BeanMapperUtils.filterProperties(propertyMap, mapper);
        copyProperties(destParent, dest, src, propertyMap);
    }

    /**
     * copy indicated property value from src to dest.
     * @param destParent
     * @param dest
     * @param src
     * @param propertyMap
     * @throws Exception
     */
    public final void copyProperties(Object destParent, Object dest, Object src, Map<String, String> propertyMap) throws Exception {
        if (dest == null || src == null) {
            return ;
        }
        for (String propertyName : propertyMap.keySet()) {
            String mappedPropertyName = propertyMap.get(propertyName);
            if (!PropertyUtils.isReadable(src, propertyName)) {
                continue ;
            }
            Object value = PropertyUtils.getProperty(src, propertyName);
            if (value == null) {
                continue;
            }
            if (destParent != null && value == destParent) {
                PropertyUtils.setProperty(dest, mappedPropertyName, destParent);
                break;
            }
            Class<?> propertyType = PropertyUtils.getPropertyType(src, propertyName);
            for (PropertyCopier copier : copierList) {
                int result = copier.copyProperty(dest, mappedPropertyName, propertyType, value);
                if (result > 0) {
                    break;
                }
            }
        }
    }


}

